{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T01:43:34.292221800Z",
     "start_time": "2024-08-01T01:43:23.962307200Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:43.146285Z",
     "iopub.status.busy": "2025-01-25T07:24:43.145948Z",
     "iopub.status.idle": "2025-01-25T07:24:48.468045Z",
     "shell.execute_reply": "2025-01-25T07:24:48.467499Z",
     "shell.execute_reply.started": "2025-01-25T07:24:43.146260Z"
    },
    "id": "WKUPkA0TJzbW",
    "outputId": "909eba98-db9c-482f-fb6b-b20b68bce069",
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n",
    "torch.manual_seed(seed)\n",
    "torch.cuda.manual_seed_all(seed)\n",
    "np.random.seed(seed)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "pSNcMyqvJzbY"
   },
   "source": [
    "## 数据加载"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T01:43:34.808811500Z",
     "start_time": "2024-08-01T01:43:34.296202600Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:48.469430Z",
     "iopub.status.busy": "2025-01-25T07:24:48.468928Z",
     "iopub.status.idle": "2025-01-25T07:24:48.581101Z",
     "shell.execute_reply": "2025-01-25T07:24:48.580551Z",
     "shell.execute_reply.started": "2025-01-25T07:24:48.469409Z"
    },
    "id": "Dm1DrSCLJzbZ",
    "outputId": "ab7f8ef9-6119-4d2b-df23-7cc04f617802"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "May I borrow this book?\n",
      "¿Puedo tomar prestado este libro?\n"
     ]
    }
   ],
   "source": [
    "import unicodedata\n",
    "import re\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#因为西班牙语有一些是特殊字符，所以我们需要unicode转ascii，\n",
    "# 这样值变小了，因为unicode太大\n",
    "def unicode_to_ascii(s):\n",
    "    #NFD是转换方法，把每一个字节拆开，Mn是重音，所以去除\n",
    "    return ''.join(c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn')\n",
    "\n",
    "#下面我们找个样本测试一下\n",
    "# 加u代表对字符串进行unicode编码\n",
    "en_sentence = u\"May I borrow this book?\"\n",
    "sp_sentence = u\"¿Puedo tomar prestado este libro?\"\n",
    "\n",
    "print(unicode_to_ascii(en_sentence))\n",
    "print(unicode_to_ascii(sp_sentence))\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T01:43:34.818582600Z",
     "start_time": "2024-08-01T01:43:34.811808900Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:48.581949Z",
     "iopub.status.busy": "2025-01-25T07:24:48.581685Z",
     "iopub.status.idle": "2025-01-25T07:24:48.586851Z",
     "shell.execute_reply": "2025-01-25T07:24:48.586377Z",
     "shell.execute_reply.started": "2025-01-25T07:24:48.581928Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "may i borrow this book ?\n",
      "¿ puedo tomar prestado este libro ?\n",
      "b'\\xc2\\xbf puedo tomar prestado este libro ?'\n"
     ]
    }
   ],
   "source": [
    "def preprocess_sentence(w):\n",
    "    #变为小写，去掉多余的空格，变成小写，id少一些\n",
    "    w = unicode_to_ascii(w.lower().strip())\n",
    "\n",
    "    # 在单词与跟在其后的标点符号之间插入一个空格\n",
    "    # eg: \"he is a boy.\" => \"he is a boy . \"\n",
    "    # Reference:- https://stackoverflow.com/questions/3645931/python-padding-punctuation-with-white-spaces-keeping-punctuation\n",
    "    w = re.sub(r\"([?.!,¿])\", r\" \\1 \", w)\n",
    "    #因为可能有多余空格，替换为一个空格，所以处理一下\n",
    "    w = re.sub(r'[\" \"]+', \" \", w)\n",
    "\n",
    "    # 除了 (a-z, A-Z, \".\", \"?\", \"!\", \",\")，将所有字符替换为空格\n",
    "    w = re.sub(r\"[^a-zA-Z?.!,¿]+\", \" \", w)\n",
    "\n",
    "    w = w.rstrip().strip()\n",
    "\n",
    "    return w\n",
    "\n",
    "print(preprocess_sentence(en_sentence))\n",
    "print(preprocess_sentence(sp_sentence))\n",
    "print(preprocess_sentence(sp_sentence).encode('utf-8'))  #¿是占用两个字节的"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "YyJksrNmJzba"
   },
   "source": [
    "Dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T01:43:34.903646400Z",
     "start_time": "2024-08-01T01:43:34.819571400Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:48.588345Z",
     "iopub.status.busy": "2025-01-25T07:24:48.588046Z",
     "iopub.status.idle": "2025-01-25T07:24:48.591289Z",
     "shell.execute_reply": "2025-01-25T07:24:48.590835Z",
     "shell.execute_reply.started": "2025-01-25T07:24:48.588325Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(1, 4, 7), (2, 5, 8)]\n"
     ]
    }
   ],
   "source": [
    "#zip例子\n",
    "a = [[1,2],[4,5],[7,8]]\n",
    "zipped = list(zip(*a))\n",
    "print(zipped)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T01:43:34.943623500Z",
     "start_time": "2024-08-01T01:43:34.832584300Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:48.591932Z",
     "iopub.status.busy": "2025-01-25T07:24:48.591770Z",
     "iopub.status.idle": "2025-01-25T07:24:48.598266Z",
     "shell.execute_reply": "2025-01-25T07:24:48.597707Z",
     "shell.execute_reply.started": "2025-01-25T07:24:48.591915Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array(['train', 'test', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'test', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'train', 'test', 'test',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'test', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'test', 'train', 'test', 'train', 'train', 'test',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'test',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train', 'train', 'train', 'train', 'train', 'train',\n",
       "       'train', 'train'], dtype='<U5')"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "split_index1 = np.random.choice(a=[\"train\", \"test\"], replace=True, p=[0.9, 0.1], size=100)\n",
    "split_index1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T01:43:36.768369100Z",
     "start_time": "2024-08-01T01:43:34.854672800Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:48.598922Z",
     "iopub.status.busy": "2025-01-25T07:24:48.598758Z",
     "iopub.status.idle": "2025-01-25T07:24:49.325609Z",
     "shell.execute_reply": "2025-01-25T07:24:49.325049Z",
     "shell.execute_reply.started": "2025-01-25T07:24:48.598905Z"
    },
    "id": "-VnoIKhaJzba"
   },
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "\n",
    "class LangPairDataset(Dataset):\n",
    "    fpath = Path(r\"./data_spa_en/spa.txt\") #数据文件路径\n",
    "    cache_path = Path(r\"./.cache/lang_pair.npy\") #缓存文件路径\n",
    "    split_index = np.random.choice(a=[\"train\", \"test\"], replace=True, p=[0.9, 0.1], size=118964) #按照9:1划分训练集和测试集\n",
    "    def __init__(self, mode=\"train\", cache=False):\n",
    "        if cache or not self.cache_path.exists():#如果没有缓存，或者缓存不存在，就处理一下数据\n",
    "            self.cache_path.parent.mkdir(parents=True, exist_ok=True) #创建缓存文件夹，如果存在就忽略\n",
    "            with open(self.fpath, \"r\", encoding=\"utf8\") as file:\n",
    "                lines = file.readlines()\n",
    "                lang_pair = [[preprocess_sentence(w) for w in l.split('\\t')]  for l in lines] #处理数据，变成list((src, trg))的形式\n",
    "                trg, src = zip(*lang_pair) #分离出目标语言和源语言\n",
    "                trg=np.array(trg) #转换为numpy数组\n",
    "                src=np.array(src) #转换为numpy数组\n",
    "                np.save(self.cache_path, {\"trg\": trg, \"src\": src})  #保存为npy文件,方便下次直接读取,不用再处理\n",
    "        else:\n",
    "            lang_pair = np.load(self.cache_path, allow_pickle=True).item() #读取npy文件，allow_pickle=True允许读取字典\n",
    "            trg = lang_pair[\"trg\"]\n",
    "            src = lang_pair[\"src\"]\n",
    "\n",
    "        self.trg = trg[self.split_index == mode] #按照index拿到训练集的 标签语言 --英语\n",
    "        self.src = src[self.split_index == mode] #按照index拿到训练集的源语言 --西班牙\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        return self.src[index], self.trg[index]\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.src)\n",
    "\n",
    "\n",
    "train_ds = LangPairDataset(\"train\")\n",
    "test_ds = LangPairDataset(\"test\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T01:43:37.276818100Z",
     "start_time": "2024-08-01T01:43:36.768369100Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:49.326475Z",
     "iopub.status.busy": "2025-01-25T07:24:49.326225Z",
     "iopub.status.idle": "2025-01-25T07:24:49.329464Z",
     "shell.execute_reply": "2025-01-25T07:24:49.328996Z",
     "shell.execute_reply.started": "2025-01-25T07:24:49.326455Z"
    },
    "id": "knue-PUkJzbb",
    "outputId": "86c1d3c8-8bd2-4c7f-8576-7516c4767ec2"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "source: si quieres sonar como un hablante nativo , debes estar dispuesto a practicar diciendo la misma frase una y otra vez de la misma manera en que un musico de banjo practica el mismo fraseo una y otra vez hasta que lo puedan tocar correctamente y en el tiempo esperado .\n",
      "target: if you want to sound like a native speaker , you must be willing to practice saying the same sentence over and over in the same way that banjo players practice the same phrase over and over until they can play it correctly and at the desired tempo .\n"
     ]
    }
   ],
   "source": [
    "print(\"source: {}\\ntarget: {}\".format(*train_ds[-1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "9mzBlPtGJzbb"
   },
   "source": [
    "### Tokenizer\n",
    "\n",
    "这里有两种处理方式，分别对应着 encoder 和 decoder 的 word embedding 是否共享，这里实现不共享的方案。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T01:43:37.517557400Z",
     "start_time": "2024-08-01T01:43:36.770369400Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:49.330213Z",
     "iopub.status.busy": "2025-01-25T07:24:49.330038Z",
     "iopub.status.idle": "2025-01-25T07:24:49.925652Z",
     "shell.execute_reply": "2025-01-25T07:24:49.925149Z",
     "shell.execute_reply.started": "2025-01-25T07:24:49.330195Z"
    },
    "id": "fMSIczSnJzbb"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "word count: 23715\n",
      "word count: 12500\n"
     ]
    }
   ],
   "source": [
    "from collections import Counter\n",
    "\n",
    "def get_word_idx(ds, mode=\"src\", threshold=2):\n",
    "    #载入词表，看下词表长度，词表就像英语字典\n",
    "    word2idx = {\n",
    "        \"[PAD]\": 0,     # 填充 token\n",
    "        \"[BOS]\": 1,     # begin of sentence\n",
    "        \"[UNK]\": 2,     # 未知 token\n",
    "        \"[EOS]\": 3,     # end of sentence\n",
    "    }\n",
    "    idx2word = {value: key for key, value in word2idx.items()}\n",
    "    index = len(idx2word)\n",
    "    threshold = 1  # 出现次数低于此的token舍弃\n",
    "    #如果数据集有很多个G，那是用for循环的，不能' '.join\n",
    "    word_list = \" \".join([pair[0 if mode==\"src\" else 1] for pair in ds]).split()\n",
    "    counter = Counter(word_list) #统计词频,counter类似字典，key是单词，value是出现次数\n",
    "    print(\"word count:\", len(counter))\n",
    "\n",
    "    for token, count in counter.items():\n",
    "        if count >= threshold:#出现次数大于阈值的token加入词表\n",
    "            word2idx[token] = index #加入词表\n",
    "            idx2word[index] = token #加入反向词表\n",
    "            index += 1\n",
    "\n",
    "    return word2idx, idx2word\n",
    "\n",
    "src_word2idx, src_idx2word = get_word_idx(train_ds, \"src\") #源语言词表\n",
    "trg_word2idx, trg_idx2word = get_word_idx(train_ds, \"trg\") #目标语言词表"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T01:43:37.573524800Z",
     "start_time": "2024-08-01T01:43:37.527552800Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:49.926475Z",
     "iopub.status.busy": "2025-01-25T07:24:49.926295Z",
     "iopub.status.idle": "2025-01-25T07:24:49.973026Z",
     "shell.execute_reply": "2025-01-25T07:24:49.972551Z",
     "shell.execute_reply.started": "2025-01-25T07:24:49.926456Z"
    },
    "id": "9_IjY_wIJzbb",
    "outputId": "f2bf8be3-ec47-48e2-b743-1d2dbd511adc"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "raw text----------\n",
      "['hello', 'world']\n",
      "['tokenize', 'text', 'datas', 'with', 'batch']\n",
      "['this', 'is', 'a', 'test']\n",
      "mask----------\n",
      "tensor([0, 0, 0, 0, 1, 1, 1])\n",
      "tensor([0, 0, 0, 0, 0, 0, 0])\n",
      "tensor([0, 0, 0, 0, 0, 0, 1])\n",
      "indices----------\n",
      "tensor([   1,   16, 3218,    3,    0,    0,    0])\n",
      "tensor([   1,    2, 3878,    2,  552,    2,    3])\n",
      "tensor([   1,  117,  235,  103, 2896,    3,    0])\n",
      "decode text----------\n",
      "[BOS] hello world [EOS] [PAD] [PAD] [PAD]\n",
      "[BOS] [UNK] text [UNK] with [UNK] [EOS]\n",
      "[BOS] this is a test [EOS] [PAD]\n"
     ]
    }
   ],
   "source": [
    "class Tokenizer:\n",
    "    def __init__(self, word2idx, idx2word, max_length=500, pad_idx=0, bos_idx=1, eos_idx=3, unk_idx=2):\n",
    "        self.word2idx = word2idx\n",
    "        self.idx2word = idx2word\n",
    "        self.max_length = max_length\n",
    "        self.pad_idx = pad_idx\n",
    "        self.bos_idx = bos_idx\n",
    "        self.eos_idx = eos_idx\n",
    "        self.unk_idx = unk_idx\n",
    "\n",
    "    def encode(self, text_list, padding_first=False, add_bos=True, add_eos=True, return_mask=False):\n",
    "        \"\"\"如果padding_first == True，则padding加载前面，否则加载后面\n",
    "        return_mask: 是否返回mask(掩码），mask用于指示哪些是padding的，哪些是真实的token\n",
    "        \"\"\"\n",
    "        max_length = min(self.max_length, add_eos + add_bos + max([len(text) for text in text_list]))\n",
    "        indices_list = []\n",
    "        for text in text_list:\n",
    "            indices = [self.word2idx.get(word, self.unk_idx) for word in text[:max_length - add_bos - add_eos]] #如果词表中没有这个词，就用unk_idx代替，indices是一个list,里面是每个词的index,也就是一个样本的index\n",
    "            if add_bos:\n",
    "                indices = [self.bos_idx] + indices\n",
    "            if add_eos:\n",
    "                indices = indices + [self.eos_idx]\n",
    "            if padding_first:#padding加载前面，超参可以调\n",
    "                indices = [self.pad_idx] * (max_length - len(indices)) + indices\n",
    "            else:#padding加载后面\n",
    "                indices = indices + [self.pad_idx] * (max_length - len(indices))\n",
    "            indices_list.append(indices)\n",
    "        input_ids = torch.tensor(indices_list) #转换为tensor\n",
    "        masks = (input_ids == self.pad_idx).to(dtype=torch.int64) #mask是一个和input_ids一样大小的tensor，0代表token，1代表padding，mask用于去除padding的影响\n",
    "        return input_ids if not return_mask else (input_ids, masks)\n",
    "\n",
    "\n",
    "    def decode(self, indices_list, remove_bos=True, remove_eos=True, remove_pad=True, split=False):\n",
    "        text_list = []\n",
    "        for indices in indices_list:\n",
    "            text = []\n",
    "            for index in indices:\n",
    "                word = self.idx2word.get(index, \"[UNK]\") #如果词表中没有这个词，就用unk_idx代替\n",
    "                if remove_bos and word == \"[BOS]\":\n",
    "                    continue\n",
    "                if remove_eos and word == \"[EOS]\":#如果到达eos，就结束\n",
    "                    break\n",
    "                if remove_pad and word == \"[PAD]\":#如果到达pad，就结束\n",
    "                    break\n",
    "                text.append(word) #单词添加到列表中\n",
    "            text_list.append(\" \".join(text) if not split else text) #把列表中的单词拼接，变为一个句子\n",
    "        return text_list\n",
    "\n",
    "#两个相对于1个toknizer的好处是embedding的参数量减少\n",
    "src_tokenizer = Tokenizer(word2idx=src_word2idx, idx2word=src_idx2word) #源语言tokenizer\n",
    "trg_tokenizer = Tokenizer(word2idx=trg_word2idx, idx2word=trg_idx2word) #目标语言tokenizer\n",
    "\n",
    "# trg_tokenizer.encode([[\"hello\"], [\"hello\", \"world\"]], add_bos=True, add_eos=False,return_mask=True)\n",
    "raw_text = [\"hello world\".split(), \"tokenize text datas with batch\".split(), \"this is a test\".split()]\n",
    "indices,mask = trg_tokenizer.encode(raw_text, padding_first=False, add_bos=True, add_eos=True,return_mask=True)\n",
    "decode_text = trg_tokenizer.decode(indices.tolist(), remove_bos=False, remove_eos=False, remove_pad=False)\n",
    "print(\"raw text\"+'-'*10)\n",
    "for raw in raw_text:\n",
    "    print(raw)\n",
    "print(\"mask\"+'-'*10)\n",
    "for m in mask:\n",
    "    print(m)\n",
    "print(\"indices\"+'-'*10)\n",
    "for index in indices:\n",
    "    print(index)\n",
    "print(\"decode text\"+'-'*10)\n",
    "for decode in decode_text:\n",
    "    print(decode)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "S8BDjaa1Jzbc"
   },
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T01:43:37.581540600Z",
     "start_time": "2024-08-01T01:43:37.573524800Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:49.975304Z",
     "iopub.status.busy": "2025-01-25T07:24:49.974919Z",
     "iopub.status.idle": "2025-01-25T07:24:49.979903Z",
     "shell.execute_reply": "2025-01-25T07:24:49.979381Z",
     "shell.execute_reply.started": "2025-01-25T07:24:49.975283Z"
    },
    "id": "sPwlGzn8Jzbc"
   },
   "outputs": [],
   "source": [
    "def collate_fct(batch):\n",
    "    src_words = [pair[0].split() for pair in batch]\n",
    "    trg_words = [pair[1].split() for pair in batch]\n",
    "\n",
    "    # [PAD] [BOS] src [EOS]\n",
    "    encoder_inputs, encoder_inputs_mask = src_tokenizer.encode(\n",
    "        src_words, padding_first=True, add_bos=True, add_eos=True, return_mask=True\n",
    "        )\n",
    "\n",
    "    # [BOS] trg [PAD]\n",
    "    decoder_inputs = trg_tokenizer.encode(\n",
    "        trg_words, padding_first=False, add_bos=True, add_eos=False, return_mask=False,\n",
    "        )\n",
    "\n",
    "    # trg [EOS] [PAD]\n",
    "    decoder_labels, decoder_labels_mask = trg_tokenizer.encode(\n",
    "        trg_words, padding_first=False, add_bos=False, add_eos=True, return_mask=True\n",
    "        )\n",
    "\n",
    "    return {\n",
    "        \"encoder_inputs\": encoder_inputs.to(device=device),\n",
    "        \"encoder_inputs_mask\": encoder_inputs_mask.to(device=device),\n",
    "        \"decoder_inputs\": decoder_inputs.to(device=device),\n",
    "        \"decoder_labels\": decoder_labels.to(device=device),\n",
    "        \"decoder_labels_mask\": decoder_labels_mask.to(device=device),\n",
    "    } #当返回的数据较多时，用dict返回比较合理\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T01:43:37.647635800Z",
     "start_time": "2024-08-01T01:43:37.580542500Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:49.980776Z",
     "iopub.status.busy": "2025-01-25T07:24:49.980495Z",
     "iopub.status.idle": "2025-01-25T07:24:50.108924Z",
     "shell.execute_reply": "2025-01-25T07:24:50.108220Z",
     "shell.execute_reply.started": "2025-01-25T07:24:49.980756Z"
    },
    "id": "_JsuutYAJzbc",
    "outputId": "fd68e596-ed01-4bae-d731-c27f5a758a6c"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "encoder_inputs\n",
      "tensor([[   0,    1,   55,   67, 1056,  306,   50,    5,    3],\n",
      "        [   1,   92, 5604,   50, 2622,  489, 3758,    5,    3]],\n",
      "       device='cuda:0')\n",
      "encoder_inputs_mask\n",
      "tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
      "        [0, 0, 0, 0, 0, 0, 0, 0, 0]], device='cuda:0')\n",
      "decoder_inputs\n",
      "tensor([[   1,   17,   32,  516,   30, 1088, 1577,    5,    0],\n",
      "        [   1,   47, 2976,  689, 5400, 2238,  634,   29,    5]],\n",
      "       device='cuda:0')\n",
      "decoder_labels\n",
      "tensor([[  17,   32,  516,   30, 1088, 1577,    5,    3,    0],\n",
      "        [  47, 2976,  689, 5400, 2238,  634,   29,    5,    3]],\n",
      "       device='cuda:0')\n",
      "decoder_labels_mask\n",
      "tensor([[0, 0, 0, 0, 0, 0, 0, 0, 1],\n",
      "        [0, 0, 0, 0, 0, 0, 0, 0, 0]], device='cuda:0')\n"
     ]
    }
   ],
   "source": [
    "sample_dl = DataLoader(train_ds, batch_size=2, shuffle=True, collate_fn=collate_fct)\n",
    "\n",
    "for batch in sample_dl:\n",
    "    for key, value in batch.items():\n",
    "        print(key)\n",
    "        print(value)\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "K9JaKLR7Jzbc"
   },
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T01:43:37.663622200Z",
     "start_time": "2024-08-01T01:43:37.612142500Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:50.109944Z",
     "iopub.status.busy": "2025-01-25T07:24:50.109555Z",
     "iopub.status.idle": "2025-01-25T07:24:50.114124Z",
     "shell.execute_reply": "2025-01-25T07:24:50.113553Z",
     "shell.execute_reply.started": "2025-01-25T07:24:50.109916Z"
    },
    "id": "CGxzT605Jzbd"
   },
   "outputs": [],
   "source": [
    "class Encoder(nn.Module):\n",
    "    def __init__(\n",
    "        self,\n",
    "        vocab_size,\n",
    "        embedding_dim=256,\n",
    "        hidden_dim=1024,\n",
    "        num_layers=1,\n",
    "        ):\n",
    "        super().__init__()\n",
    "        self.embedding = nn.Embedding(vocab_size, embedding_dim)\n",
    "        self.gru = nn.GRU(embedding_dim, hidden_dim, num_layers=num_layers, batch_first=True)\n",
    "\n",
    "    def forward(self, encoder_inputs):\n",
    "        # encoder_inputs.shape = [batch size, sequence length]\n",
    "        # bs, seq_len = encoder_inputs.shape\n",
    "        embeds = self.embedding(encoder_inputs)\n",
    "        # embeds.shape = [batch size, sequence length, embedding_dim]->[batch size, sequence length, hidden_dim]\n",
    "        seq_output, hidden = self.gru(embeds)\n",
    "        # seq_output.shape = [batch size, sequence length, hidden_dim]，hidden.shape [ num_layers, batch size, hidden_dim]\n",
    "        return seq_output, hidden"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T02:19:55.960154200Z",
     "start_time": "2024-08-01T02:19:55.426284200Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:50.114733Z",
     "iopub.status.busy": "2025-01-25T07:24:50.114566Z",
     "iopub.status.idle": "2025-01-25T07:24:50.397204Z",
     "shell.execute_reply": "2025-01-25T07:24:50.396565Z",
     "shell.execute_reply.started": "2025-01-25T07:24:50.114715Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([2, 50, 1024])\n",
      "torch.Size([4, 2, 1024])\n",
      "tensor([[-0.0636,  0.0092,  0.0103,  ..., -0.0153, -0.0004, -0.0341],\n",
      "        [-0.0175,  0.0283,  0.0105,  ...,  0.0142,  0.0282, -0.0413]],\n",
      "       grad_fn=<SliceBackward0>)\n",
      "tensor([[-0.0636,  0.0092,  0.0103,  ..., -0.0153, -0.0004, -0.0341],\n",
      "        [-0.0175,  0.0283,  0.0105,  ...,  0.0142,  0.0282, -0.0413]],\n",
      "       grad_fn=<SliceBackward0>)\n"
     ]
    }
   ],
   "source": [
    "#把上面的Encoder写一个例子，看看输出的shape\n",
    "encoder = Encoder(vocab_size=100, embedding_dim=256, hidden_dim=1024, num_layers=4)\n",
    "encoder_inputs = torch.randint(0, 100, (2, 50))\n",
    "encoder_outputs, hidden = encoder(encoder_inputs)\n",
    "print(encoder_outputs.shape)\n",
    "print(hidden.shape)\n",
    "print(encoder_outputs[:,-1,:])\n",
    "print(hidden[-1,:,:]) #取最后一层的hidden"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-05-06T01:57:05.054653Z",
     "start_time": "2024-05-06T01:57:04.982694100Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:50.398490Z",
     "iopub.status.busy": "2025-01-25T07:24:50.397986Z",
     "iopub.status.idle": "2025-01-25T07:24:50.403404Z",
     "shell.execute_reply": "2025-01-25T07:24:50.402942Z",
     "shell.execute_reply.started": "2025-01-25T07:24:50.398458Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([2, 1, 1024])"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "query1 = torch.randn(2, 1024)\n",
    "query1.unsqueeze(1).shape #增加维度"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T02:19:58.695774900Z",
     "start_time": "2024-08-01T02:19:58.676428200Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:50.404460Z",
     "iopub.status.busy": "2025-01-25T07:24:50.404028Z",
     "iopub.status.idle": "2025-01-25T07:24:50.410419Z",
     "shell.execute_reply": "2025-01-25T07:24:50.409857Z",
     "shell.execute_reply.started": "2025-01-25T07:24:50.404431Z"
    },
    "id": "pTQ6Mz1OJzbd"
   },
   "outputs": [],
   "source": [
    "class BahdanauAttention(nn.Module):\n",
    "    def __init__(self, hidden_dim=1024):\n",
    "        super().__init__()\n",
    "        self.Wk = nn.Linear(hidden_dim, hidden_dim) #对keys做运算，encoder的输出EO\n",
    "        self.Wq = nn.Linear(hidden_dim, hidden_dim) #对query做运算，decoder的隐藏状态\n",
    "        self.V = nn.Linear(hidden_dim, 1)\n",
    "\n",
    "    def forward(self, query, keys, values, attn_mask=None):\n",
    "        \"\"\"\n",
    "        正向传播\n",
    "        :param query: hidden state，是decoder的隐藏状态，shape = [batch size, hidden_dim]\n",
    "        :param keys: EO  [batch size, sequence length, hidden_dim]\n",
    "        :param values: EO  [batch size, sequence length, hidden_dim]\n",
    "        :param attn_mask:[batch size, sequence length]\n",
    "        :return:\n",
    "        \"\"\"\n",
    "        # query.shape = [batch size, hidden_dim] -->通过unsqueeze(-2)增加维度 [batch size, 1, hidden_dim]\n",
    "        # keys.shape = [batch size, sequence length, hidden_dim]\n",
    "        # values.shape = [batch size, sequence length, hidden_dim]\n",
    "        scores = self.V(F.tanh(self.Wk(keys) + self.Wq(query.unsqueeze(-2)))) #unsqueeze(-2)增加维度\n",
    "        # score.shape = [batch size, sequence length, 1]\n",
    "        if attn_mask is not None: #这个mask是encoder_inputs_mask，用来mask掉padding的部分,让padding部分socres为0\n",
    "            # attn_mask is a matrix of 0/1 element,\n",
    "            # 1 means to mask logits while 0 means do nothing\n",
    "            # here we add -inf to the element while mask == 1\n",
    "            attn_mask = (attn_mask.unsqueeze(-1)) * -1e16 #在最后增加一个维度，[batch size, sequence length] --> [batch size, sequence length, 1]\n",
    "            scores += attn_mask\n",
    "        scores = F.softmax(scores, dim=-2) #对每一个词的score做softmax\n",
    "        # score.shape = [batch size, sequence length, 1]\n",
    "        context_vector = torch.mul(scores, values).sum(dim=-2) #对每一个词的score和对应的value做乘法，然后在seq_len维度上求和，得到context_vector\n",
    "        # context_vector.shape = [batch size, hidden_dim]\n",
    "        #socres用于最后的画图\n",
    "        return context_vector, scores\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-05-06T01:57:05.161598600Z",
     "start_time": "2024-05-06T01:57:05.041662700Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:50.411610Z",
     "iopub.status.busy": "2025-01-25T07:24:50.411167Z",
     "iopub.status.idle": "2025-01-25T07:24:50.418315Z",
     "shell.execute_reply": "2025-01-25T07:24:50.417781Z",
     "shell.execute_reply.started": "2025-01-25T07:24:50.411581Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([2, 2])\n"
     ]
    }
   ],
   "source": [
    "#tensor矩阵相乘\n",
    "a = torch.randn(2, 3)\n",
    "b = torch.randn(3, 2)\n",
    "c = torch.mm(a, b) #增加维度\n",
    "print(c.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T02:49:32.095682500Z",
     "start_time": "2024-08-01T02:49:32.051271600Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:50.419272Z",
     "iopub.status.busy": "2025-01-25T07:24:50.418910Z",
     "iopub.status.idle": "2025-01-25T07:24:50.446450Z",
     "shell.execute_reply": "2025-01-25T07:24:50.445906Z",
     "shell.execute_reply.started": "2025-01-25T07:24:50.419242Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([2, 1024])\n",
      "torch.Size([2, 50, 1])\n"
     ]
    }
   ],
   "source": [
    "#把上面的BahdanauAttention写一个例子，看看输出的shape\n",
    "attention = BahdanauAttention(hidden_dim=1024)\n",
    "query = torch.randn(2, 1024) #Decoder的隐藏状态\n",
    "keys = torch.randn(2, 50, 1024) #EO\n",
    "values = torch.randn(2, 50, 1024) #EO\n",
    "attn_mask = torch.randint(0, 2, (2, 50))\n",
    "context_vector, scores = attention(query, keys, values, attn_mask)\n",
    "print(context_vector.shape)\n",
    "print(scores.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T03:02:48.940488500Z",
     "start_time": "2024-08-01T03:02:48.937601400Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:50.447490Z",
     "iopub.status.busy": "2025-01-25T07:24:50.447072Z",
     "iopub.status.idle": "2025-01-25T07:24:50.453665Z",
     "shell.execute_reply": "2025-01-25T07:24:50.453196Z",
     "shell.execute_reply.started": "2025-01-25T07:24:50.447468Z"
    },
    "id": "6W5FeRRrJzbd"
   },
   "outputs": [],
   "source": [
    "class Decoder(nn.Module):\n",
    "    def __init__(\n",
    "        self,\n",
    "        vocab_size,\n",
    "        embedding_dim=256,\n",
    "        hidden_dim=1024,\n",
    "        num_layers=1,\n",
    "        ):\n",
    "        super(Decoder, self).__init__()\n",
    "        self.embedding = nn.Embedding(vocab_size, embedding_dim)\n",
    "        self.gru = nn.GRU(embedding_dim + hidden_dim, hidden_dim, num_layers=num_layers, batch_first=True)\n",
    "        self.fc = nn.Linear(hidden_dim, vocab_size) #最后分类\n",
    "        self.dropout = nn.Dropout(0.6)\n",
    "        self.attention = BahdanauAttention(hidden_dim) #注意力得到的context_vector\n",
    "\n",
    "    def forward(self, decoder_input, hidden, encoder_outputs, attn_mask=None):\n",
    "        #attn_mask是encoder_inputs_mask\n",
    "        # decoder_input.shape = [batch size, 1]\n",
    "        assert len(decoder_input.shape) == 2 and decoder_input.shape[-1] == 1, f\"decoder_input.shape = {decoder_input.shape}\"\n",
    "        # hidden.shape = [batch size, hidden_dim]，decoder_hidden,而第一次使用的是encoder的hidden\n",
    "        assert len(hidden.shape) == 2, f\"hidden.shape = {hidden.shape}\"\n",
    "        # encoder_outputs.shape = [batch size, sequence length, hidden_dim]\n",
    "        assert len(encoder_outputs.shape) == 3, f\"encoder_outputs.shape = {encoder_outputs.shape}\"\n",
    "\n",
    "        context_vector, attention_score = self.attention(\n",
    "            query=hidden, keys=encoder_outputs, values=encoder_outputs, attn_mask=attn_mask)\n",
    "        # context_vector.shape = [batch size, hidden_dim]\n",
    "        embeds = self.embedding(decoder_input)\n",
    "        # embeds.shape = [batch size, 1, embedding_dim]\n",
    "        embeds = torch.cat([context_vector.unsqueeze(-2), embeds], dim=-1)\n",
    "        # embeds.shape = [batch size, 1, embedding_dim + hidden_dim]\n",
    "        seq_output, hidden = self.gru(embeds)\n",
    "        # seq_output.shape = [batch size, 1, hidden_dim]\n",
    "        logits = self.fc(self.dropout(seq_output))\n",
    "        # logits.shape = [batch size, 1, vocab size]，attention_score = [batch size, sequence length, 1]\n",
    "        return logits, hidden, attention_score\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T03:26:47.631513900Z",
     "start_time": "2024-08-01T03:26:47.613513600Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:50.454627Z",
     "iopub.status.busy": "2025-01-25T07:24:50.454263Z",
     "iopub.status.idle": "2025-01-25T07:24:50.463868Z",
     "shell.execute_reply": "2025-01-25T07:24:50.463421Z",
     "shell.execute_reply.started": "2025-01-25T07:24:50.454607Z"
    },
    "id": "FG-Pid9cJzbd"
   },
   "outputs": [],
   "source": [
    "class Sequence2Sequence(nn.Module):\n",
    "    def __init__(\n",
    "        self,\n",
    "        src_vocab_size, #输入词典大小\n",
    "        trg_vocab_size, #输出词典大小\n",
    "        encoder_embedding_dim=256,\n",
    "        encoder_hidden_dim=1024,\n",
    "        encoder_num_layers=1,\n",
    "        decoder_embedding_dim=256,\n",
    "        decoder_hidden_dim=1024,\n",
    "        decoder_num_layers=1,\n",
    "        bos_idx=1,\n",
    "        eos_idx=3,\n",
    "        max_length=512,\n",
    "        ):\n",
    "        super(Sequence2Sequence, self).__init__()\n",
    "        self.bos_idx = bos_idx\n",
    "        self.eos_idx = eos_idx\n",
    "        self.max_length = max_length\n",
    "        self.encoder = Encoder(\n",
    "            src_vocab_size,\n",
    "            embedding_dim=encoder_embedding_dim,\n",
    "            hidden_dim=encoder_hidden_dim,\n",
    "            num_layers=encoder_num_layers,\n",
    "            )\n",
    "        self.decoder = Decoder(\n",
    "            trg_vocab_size,\n",
    "            embedding_dim=decoder_embedding_dim,\n",
    "            hidden_dim=decoder_hidden_dim,\n",
    "            num_layers=decoder_num_layers,\n",
    "            )\n",
    "\n",
    "    def forward(self, *, encoder_inputs, decoder_inputs, attn_mask=None):\n",
    "        # encoding\n",
    "        encoder_outputs, hidden = self.encoder(encoder_inputs)\n",
    "        # decoding with teacher forcing\n",
    "        bs, seq_len = decoder_inputs.shape\n",
    "        logits_list = []\n",
    "        scores_list = []\n",
    "        for i in range(seq_len):#串行训练\n",
    "            # 每次迭代生成一个时间步的预测，存储在 logits_list 中，并且记录注意力分数（如果有的话）在 scores_list 中，最后将预测的logits和注意力分数拼接并返回。\n",
    "            logits, hidden, score = self.decoder(\n",
    "                decoder_inputs[:, i:i+1],\n",
    "                hidden[-1], #取最后一层的hidden\n",
    "                encoder_outputs,\n",
    "                attn_mask=attn_mask\n",
    "                )\n",
    "            logits_list.append(logits) #记录预测的logits，用于计算损失\n",
    "            scores_list.append(score) #记录注意力分数,用于画图\n",
    "\n",
    "        return torch.cat(logits_list, dim=-2), torch.cat(scores_list, dim=-1)\n",
    "\n",
    "    @torch.no_grad() #不计算梯度\n",
    "    def infer(self, encoder_input, attn_mask=None):\n",
    "        #infer用于预测\n",
    "        # encoder_input.shape = [1, sequence length]\n",
    "        # encoding\n",
    "        encoder_outputs, hidden = self.encoder(encoder_input)\n",
    "\n",
    "        # decoding，[[1]]\n",
    "        decoder_input = torch.Tensor([self.bos_idx]).reshape(1, 1).to(dtype=torch.int64) #shape为[1,1]，内容为开始标记\n",
    "        decoder_pred = None\n",
    "        pred_list = [] #预测序列\n",
    "        score_list = []\n",
    "        # 从开始标记 bos_idx 开始，迭代地生成序列，直到生成结束标记 eos_idx 或达到最大长度 max_length。\n",
    "        for _ in range(self.max_length):\n",
    "            logits, hidden, score = self.decoder(\n",
    "                decoder_input,\n",
    "                hidden[-1],\n",
    "                encoder_outputs,\n",
    "                attn_mask=attn_mask\n",
    "                )\n",
    "            # using greedy search\n",
    "            decoder_pred = logits.argmax(dim=-1)\n",
    "            decoder_input = decoder_pred\n",
    "            pred_list.append(decoder_pred.reshape(-1).item()) #decoder_pred从(1,1)变为（1）标量\n",
    "            score_list.append(score) #记录注意力分数,用于画图\n",
    "\n",
    "            # stop at eos token\n",
    "            if decoder_pred == self.eos_idx:\n",
    "                break\n",
    "\n",
    "        # return\n",
    "        return pred_list, torch.cat(score_list, dim=-1)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zE-vNp-xJzbe"
   },
   "source": [
    "## 训练"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "o55JWSvhJzbe"
   },
   "source": [
    "### 损失函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T03:38:58.545747900Z",
     "start_time": "2024-08-01T03:38:58.543749300Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:50.464707Z",
     "iopub.status.busy": "2025-01-25T07:24:50.464416Z",
     "iopub.status.idle": "2025-01-25T07:24:50.468616Z",
     "shell.execute_reply": "2025-01-25T07:24:50.468169Z",
     "shell.execute_reply.started": "2025-01-25T07:24:50.464686Z"
    },
    "id": "c_Mmw5GAJzbe"
   },
   "outputs": [],
   "source": [
    "def cross_entropy_with_padding(logits, labels, padding_mask=None):\n",
    "    # logits.shape = [batch size, sequence length, num of classes]\n",
    "    # labels.shape = [batch size, sequence length]\n",
    "    # padding_mask.shape = [batch size, sequence length]\n",
    "    bs, seq_len, nc = logits.shape\n",
    "    loss = F.cross_entropy(logits.reshape(bs * seq_len, nc), labels.reshape(-1), reduce=False) #reduce=False表示不对batch求平均\n",
    "    if padding_mask is None:#如果没有padding_mask，就直接求平均\n",
    "        loss = loss.mean()\n",
    "    else:\n",
    "        # 如果提供了 padding_mask，则将填充部分的损失去除后计算有效损失的均值。首先，通过将 padding_mask reshape 成一维张量，并取 1 减去得到填充掩码。这样填充部分的掩码值变为 1，非填充部分变为 0。将损失张量与填充掩码相乘，这样填充部分的损失就会变为 0。然后，计算非填充部分的损失和（sum）以及非填充部分的掩码数量（sum）作为有效损失的均值计算。(因为上面我们设计的mask的token是0，所以这里是1-padding_mask)\n",
    "        padding_mask = 1 - padding_mask.reshape(-1) #将padding_mask reshape成一维张量，mask部分为0，非mask部分为1\n",
    "        loss = torch.mul(loss, padding_mask).sum() / padding_mask.sum()\n",
    "\n",
    "    return loss\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ITY9VUiiJzbe"
   },
   "source": [
    "### Callback"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T06:32:47.905030900Z",
     "start_time": "2024-08-01T06:32:37.847839900Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:50.469268Z",
     "iopub.status.busy": "2025-01-25T07:24:50.469100Z",
     "iopub.status.idle": "2025-01-25T07:24:54.864283Z",
     "shell.execute_reply": "2025-01-25T07:24:54.863738Z",
     "shell.execute_reply.started": "2025-01-25T07:24:50.469250Z"
    },
    "id": "qez1fjOPJzbe"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-01-25 15:24:51.348300: I tensorflow/core/util/port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\n",
      "2025-01-25 15:24:51.741849: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:477] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n",
      "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n",
      "E0000 00:00:1737789891.884923     300 cuda_dnn.cc:8310] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n",
      "E0000 00:00:1737789891.927929     300 cuda_blas.cc:1418] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n",
      "2025-01-25 15:24:52.293891: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n",
      "To enable the following instructions: AVX2 AVX512F AVX512_VNNI FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n"
     ]
    }
   ],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "\n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\",\n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "\n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "\n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "\n",
    "        )\n",
    "\n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T06:32:52.240896200Z",
     "start_time": "2024-08-01T06:32:52.234865400Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:54.865475Z",
     "iopub.status.busy": "2025-01-25T07:24:54.864908Z",
     "iopub.status.idle": "2025-01-25T07:24:54.870452Z",
     "shell.execute_reply": "2025-01-25T07:24:54.869971Z",
     "shell.execute_reply.started": "2025-01-25T07:24:54.865454Z"
    },
    "id": "wXtxS8ukJzbe"
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch.\n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = - np.inf\n",
    "\n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T06:32:56.655315900Z",
     "start_time": "2024-08-01T06:32:56.645314800Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:54.871358Z",
     "iopub.status.busy": "2025-01-25T07:24:54.871008Z",
     "iopub.status.idle": "2025-01-25T07:24:54.878319Z",
     "shell.execute_reply": "2025-01-25T07:24:54.877874Z",
     "shell.execute_reply.started": "2025-01-25T07:24:54.871339Z"
    },
    "id": "lfzfWswRJzbe"
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute\n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = - np.inf\n",
    "        self.counter = 0\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter\n",
    "            self.counter = 0\n",
    "        else:\n",
    "            self.counter += 1\n",
    "\n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "F2f3S6z7Jzbf"
   },
   "source": [
    "### training & valuating"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-05-06T02:18:16.897674100Z",
     "start_time": "2024-05-06T02:18:16.874685800Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:24:54.879342Z",
     "iopub.status.busy": "2025-01-25T07:24:54.878893Z",
     "iopub.status.idle": "2025-01-25T07:24:54.883105Z",
     "shell.execute_reply": "2025-01-25T07:24:54.882641Z",
     "shell.execute_reply.started": "2025-01-25T07:24:54.879324Z"
    },
    "id": "IOlJp26YJzbf",
    "tags": []
   },
   "outputs": [],
   "source": [
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for batch in dataloader:\n",
    "        encoder_inputs = batch[\"encoder_inputs\"]\n",
    "        encoder_inputs_mask = batch[\"encoder_inputs_mask\"]\n",
    "        decoder_inputs = batch[\"decoder_inputs\"]\n",
    "        decoder_labels = batch[\"decoder_labels\"]\n",
    "        decoder_labels_mask = batch[\"decoder_labels_mask\"]\n",
    "\n",
    "        # 前向计算\n",
    "        logits, _ = model(\n",
    "            encoder_inputs=encoder_inputs,\n",
    "            decoder_inputs=decoder_inputs,\n",
    "            attn_mask=encoder_inputs_mask\n",
    "            ) #model就是seq2seq模型\n",
    "        loss = loss_fct(logits, decoder_labels, padding_mask=decoder_labels_mask)         # 验证集损失\n",
    "        loss_list.append(loss.cpu().item())\n",
    "\n",
    "    return np.mean(loss_list)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 101,
     "referenced_widgets": [
      "267a9f8d838c4649b208914938b1bcff",
      "a9552c62dcb4441fbca47f89e939759d",
      "7dc472c2f73f4443a0e8437074e67476",
      "c46cefacefb54be7ad60ca93855de3ca",
      "9ebbd2d91c9849baaa85cff5b2b048e1",
      "63c6ee4650cb4a938e9f325113e259d1",
      "95d9e57c587f43e6ba5f656f2b2e5c71",
      "863e7ad581894502979884fe0e9e412e",
      "7d21bf5788794ed2b0b4dc4a98a5d2e9",
      "296304e90e43440094ba467cafb20896",
      "c3d8fddae7354c278c8e88291dbcee8d"
     ]
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:27:01.735788Z",
     "iopub.status.busy": "2025-01-25T07:27:01.735422Z",
     "iopub.status.idle": "2025-01-25T07:27:03.773916Z",
     "shell.execute_reply": "2025-01-25T07:27:03.773311Z",
     "shell.execute_reply.started": "2025-01-25T07:27:01.735762Z"
    },
    "id": "brzx2uFHJzbf",
    "outputId": "6c482ef7-5b4c-41e8-954f-6e9d0a034d1b",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 1\n",
    "    model.train() # 切换到训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for batch in train_loader:\n",
    "                encoder_inputs = batch[\"encoder_inputs\"]\n",
    "                encoder_inputs_mask = batch[\"encoder_inputs_mask\"]\n",
    "                decoder_inputs = batch[\"decoder_inputs\"]\n",
    "                decoder_labels = batch[\"decoder_labels\"]\n",
    "                decoder_labels_mask = batch[\"decoder_labels_mask\"]\n",
    "\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "\n",
    "                # 前向计算\n",
    "                logits, _ = model(\n",
    "                    encoder_inputs=encoder_inputs,\n",
    "                    decoder_inputs=decoder_inputs,\n",
    "                    attn_mask=encoder_inputs_mask\n",
    "                    )\n",
    "                loss = loss_fct(logits, decoder_labels, padding_mask=decoder_labels_mask)\n",
    "\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "\n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval() # 切换到验证模式\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train() # 切换到训练模式\n",
    "\n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step,\n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=-val_loss)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "\n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "            pbar.set_postfix({\"epoch\": epoch_id, \"loss\": loss, \"val_loss\": val_loss}) # 更新进度条\n",
    "\n",
    "    return record_dict\n",
    "\n",
    "\n",
    "epoch = 20\n",
    "batch_size = 64\n",
    "\n",
    "model = Sequence2Sequence(src_vocab_size=len(src_word2idx), trg_vocab_size=len(trg_word2idx))\n",
    "train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True, collate_fn=collate_fct)\n",
    "test_dl = DataLoader(test_ds, batch_size=batch_size, shuffle=False, collate_fn=collate_fct)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = cross_entropy_with_padding\n",
    "# 2. 定义优化器 采用 adam\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "exp_name = \"translate-seq2seq\"\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/{exp_name}\")\n",
    "# tensorboard_callback.draw_model(model, [1, MAX_LENGTH])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(\n",
    "    f\"checkpoints/{exp_name}\", save_step=200, save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# record = training(\n",
    "#     model,\n",
    "#     train_dl,\n",
    "#     test_dl,\n",
    "#     epoch,\n",
    "#     loss_fct,\n",
    "#     optimizer,\n",
    "#     tensorboard_callback=None,\n",
    "#     save_ckpt_callback=save_ckpt_callback,\n",
    "#     early_stop_callback=early_stop_callback,\n",
    "#     eval_step=200\n",
    "#     )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T07:21:09.603023900Z",
     "start_time": "2024-08-01T07:21:09.587018500Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-23T09:45:23.277164Z",
     "iopub.status.busy": "2025-01-23T09:45:23.276592Z",
     "iopub.status.idle": "2025-01-23T09:45:23.280627Z",
     "shell.execute_reply": "2025-01-23T09:45:23.280188Z",
     "shell.execute_reply.started": "2025-01-23T09:45:23.277141Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "35212249"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#计算模型参数量\n",
    "sum(i[1].numel() for i in model.named_parameters())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T06:51:34.906600900Z",
     "start_time": "2024-08-01T06:51:34.891495800Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-23T09:45:23.281458Z",
     "iopub.status.busy": "2025-01-23T09:45:23.281272Z",
     "iopub.status.idle": "2025-01-23T09:45:23.284746Z",
     "shell.execute_reply": "2025-01-23T09:45:23.284327Z",
     "shell.execute_reply.started": "2025-01-23T09:45:23.281437Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1676.0"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "33520/20"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T06:50:49.447830800Z",
     "start_time": "2024-08-01T06:50:49.438888Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-23T09:45:23.285508Z",
     "iopub.status.busy": "2025-01-23T09:45:23.285229Z",
     "iopub.status.idle": "2025-01-23T09:45:23.288449Z",
     "shell.execute_reply": "2025-01-23T09:45:23.288032Z",
     "shell.execute_reply.started": "2025-01-23T09:45:23.285490Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1672.93125"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "118964*0.9/64"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 430
    },
    "execution": {
     "iopub.execute_input": "2025-01-23T09:45:23.289221Z",
     "iopub.status.busy": "2025-01-23T09:45:23.288943Z",
     "iopub.status.idle": "2025-01-23T09:45:23.378925Z",
     "shell.execute_reply": "2025-01-23T09:45:23.378431Z",
     "shell.execute_reply.started": "2025-01-23T09:45:23.289203Z"
    },
    "id": "mAKeaApNJzbf",
    "outputId": "2933f0b5-29b1-47bc-a6dd-63350da513ca"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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ERAQ4Pa6GgULnU6HPn36YPPmzfZjTz75JJKTk7FlyxaXz3HVYhEbG4vMzMxKC1ZTr/x2EAuTU/Ho4Fb456hOHrsv1Y7RaERSUhJGjhwJrVYrd3EIrJPGhvXRuLA+qmYwGBAWFlZlsKhRV0iLFi3QpUsXh2OdO3fGL7/84vY5er0eer3e6bhWq/Vo5anVagCASqXmX4pGxNP1THXHOmlcWB+NC+vDveq+LzWabjpw4ECkpKQ4HDt69ChatWpVk9vUK043JSIikk+NgsXTTz+NrVu34o033sDx48excOFCfPbZZ5g6dWp9la/aDEXWQSVnLhfKXBIiIqLmq0bBom/fvli6dCkWLVqEhIQEzJo1C++//z4mT55cX+Wrtv/tvQgAWHbgkswlISIiar5qNMYCAK677jpcd9119VEWIiIiauKUt1cIERERyYbBgoiIiDyGwYKIiIg8hsGCiIiIPIbBgoiIiDxGMcHi6eHtAQDtwn1lLgkREVHzpZhgERviDQCI8HdePpyIiIgahmKChVqSAABmC5f0JiIikotigoVKZQ0WecUmmUtCRETUfCkmWKSk5QEADpd9JSIiooanmGAR7KuTuwhERETNnmKCRa/YIABAiG/19osnIiIiz1NMsNBrrD+K4NhNIiIi2SgmWOi01h+l1GyRuSRERETNl3KChdr6oxSUmLH5RKbMpSEiImqeFBMsbF0hADDp820yloSIiKj5Ukyw0GkU86MQERE1WYr5NNYzWBAREclOMZ/GmrKVN4mIiEg+igkWksRgQUREJDfFBAsiIiKSH4MFEREReQyDBREREXkMgwURERF5jGKDxe6z2XIXgYiIqNlRbLB4YtFuuYtARETU7Cg2WHCXUyIiooan2GBBREREDU9RweKq0PIt04uNZhlLQkRE1DwpKljEB5b3f1wuKJWxJERERM2TooKFj0buEhARETVvigoWV+5DVmJidwgREVFDUlSwuHIfslOZBfIUhIiIqJlSVLCoOMaCiIiIGp6igoXmip9GArdSJyIiakiKChZEREQkL8UFC32FZosrx1wQERFR/VJcsFBfOTWEiIiIGozigkVhafkU0xKjpZIriYiIyNMUFywqevrHPXIXgYiIqFlRdLA4np6P7aey5C4GERFRs6HoYAEAt366Re4iEBERNRuKDxZERETUcBQXLDjFlIiISD7KCxZy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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot([i[\"step\"] for i in record[\"train\"]], [i[\"loss\"] for i in record[\"train\"]], label=\"train\")\n",
    "plt.plot([i[\"step\"] for i in record[\"val\"]], [i[\"loss\"] for i in record[\"val\"]], label=\"val\")\n",
    "plt.grid()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "UOSROA66Jzbg"
   },
   "source": [
    "## 推理\n",
    "\n",
    "- 翻译项目的评估指标一般是BLEU4，感兴趣的同学自行了解并实现\n",
    "- 接下来进行翻译推理，并作出注意力的热度图"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:27:18.122424Z",
     "iopub.status.busy": "2025-01-25T07:27:18.121782Z",
     "iopub.status.idle": "2025-01-25T07:27:18.249462Z",
     "shell.execute_reply": "2025-01-25T07:27:18.248825Z",
     "shell.execute_reply.started": "2025-01-25T07:27:18.122396Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "best.ckpt\n"
     ]
    }
   ],
   "source": [
    "!ls checkpoints/translate-seq2seq"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T06:54:16.682669600Z",
     "start_time": "2024-08-01T06:54:14.942826400Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 875
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:27:20.930961Z",
     "iopub.status.busy": "2025-01-25T07:27:20.930585Z",
     "iopub.status.idle": "2025-01-25T07:27:21.462970Z",
     "shell.execute_reply": "2025-01-25T07:27:21.462324Z",
     "shell.execute_reply.started": "2025-01-25T07:27:20.930937Z"
    },
    "id": "cX75BqcBJzbg",
    "outputId": "e9dcbb2c-5188-4f2e-a1ac-793fe5f40b19",
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_300/788989974.py:3: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
      "  model.load_state_dict(torch.load(f\"./checkpoints/translate-seq2seq/best.ckpt\", map_location=\"cpu\"))\n"
     ]
    }
   ],
   "source": [
    "# load checkpoints,如何上线\n",
    "model = Sequence2Sequence(len(src_word2idx), len(trg_word2idx))\n",
    "model.load_state_dict(torch.load(f\"./checkpoints/translate-seq2seq/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "class Translator:\n",
    "    def __init__(self, model, src_tokenizer, trg_tokenizer):\n",
    "        self.model = model\n",
    "        self.model.eval() # 切换到验证模式\n",
    "        self.src_tokenizer = src_tokenizer\n",
    "        self.trg_tokenizer = trg_tokenizer\n",
    "\n",
    "    def draw_attention_map(self, scores, src_words_list, trg_words_list):\n",
    "        \"\"\"绘制注意力热力图\n",
    "\n",
    "        Args:\n",
    "            - scores (numpy.ndarray): shape = [source sequence length, target sequence length]\n",
    "        \"\"\"\n",
    "        plt.matshow(scores.T, cmap='viridis') # 注意力矩阵,显示注意力分数值\n",
    "        # 获取当前的轴\n",
    "        ax = plt.gca()\n",
    "\n",
    "        # 设置热图中每个单元格的分数的文本\n",
    "        for i in range(scores.shape[0]): #输入\n",
    "            for j in range(scores.shape[1]): #输出\n",
    "                ax.text(j, i, f'{scores[i, j]:.2f}',  # 格式化数字显示\n",
    "                               ha='center', va='center', color='k')\n",
    "\n",
    "        plt.xticks(range(scores.shape[0]), src_words_list)\n",
    "        plt.yticks(range(scores.shape[1]), trg_words_list)\n",
    "        plt.show()\n",
    "\n",
    "    def __call__(self, sentence):\n",
    "        sentence = preprocess_sentence(sentence) # 预处理句子，标点符号处理等\n",
    "        encoder_input, attn_mask = self.src_tokenizer.encode(\n",
    "            [sentence.split()],\n",
    "            padding_first=True,\n",
    "            add_bos=True,\n",
    "            add_eos=True,\n",
    "            return_mask=True,\n",
    "            ) # 对输入进行编码，并返回encode_piadding_mask\n",
    "        encoder_input = torch.Tensor(encoder_input).to(dtype=torch.int64) # 转换成tensor\n",
    "\n",
    "        preds, scores = model.infer(encoder_input=encoder_input, attn_mask=attn_mask) #预测\n",
    "\n",
    "        trg_sentence = self.trg_tokenizer.decode([preds], split=True, remove_eos=False)[0] #通过tokenizer转换成文字\n",
    "\n",
    "        src_decoded = self.src_tokenizer.decode(\n",
    "            encoder_input.tolist(),\n",
    "            split=True,\n",
    "            remove_bos=False,\n",
    "            remove_eos=False\n",
    "            )[0] #对输入编码id进行解码，转换成文字,为了画图\n",
    "\n",
    "        self.draw_attention_map(\n",
    "            scores.squeeze(0).numpy(),\n",
    "            src_decoded, # 注意力图的源句子\n",
    "            trg_sentence # 注意力图的目标句子\n",
    "            )\n",
    "        return \" \".join(trg_sentence[:-1])\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-08-01T06:54:20.377291800Z",
     "start_time": "2024-08-01T06:54:19.612529200Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-25T07:27:27.850185Z",
     "iopub.status.busy": "2025-01-25T07:27:27.849836Z",
     "iopub.status.idle": "2025-01-25T07:27:28.035357Z",
     "shell.execute_reply": "2025-01-25T07:27:28.034787Z",
     "shell.execute_reply.started": "2025-01-25T07:27:27.850163Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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hQR5eXn4fL6Ev+vJZ6GnyudMTJs2jZ07dyZ8x7p169DpdEyZMuV9tzuWPmx97O7u5sCBA/y///f/WLVqFdOmTaO3tzehv7t27SIYDMaXnThlHc5s24rU+8QnPsHrr78ev+92nKqq/PjHP2b69Okj7tedaPr06USj0YRtnQpS5M5Ad3c3bW1tHDlyhJdffpmf/vSnXHTRRbhcLhRF4be//S179+7lhhtuYNOmTTQ1NfHUU09xzTXXsGzZsvgD2C+88AKf+tSneOGFFzh48CAHDhzgRz/6ES+99BLXXnvtuOZYVVVFJBLhZz/7GYcOHeL3v//9iIkM99xzD5s3b+aLX/wiu3btYv/+/fzyl7+kq6sLp9PJP//zP3PXXXfx2GOPUV9fz7Zt2/jZz37GY489lvZ8lixZgs1m41vf+hb19fU8/vjjPProowkx9957L0888QT33nsv+/bto6amhh/+8Idn/B233HILFouF2267jd27d/Pmm2/yla98hVtvvTU+dDrePmx9zMrKIicnh4cffpi6ujreeOMN7r777vjnn/zkJ1EUhTvuuIO9e/fy0ksvjRjlOJNtey74+c9/ftrh8vF2/Nh34ut4UbrrrrtYvHgx11xzDU899RRNTU1s3ryZG264gX379vHb3/42PrqycuVKHnroIbZu3UpDQwMvvfQS3/rWt+LH0VSSIncGVq9eTVFREZWVldx5551ceeWV/PnPf45/ft5557Fhwwb0ej1XXHEFVVVV3HPPPdx22238/e9/x2w2A0NnLjabja9//evMnTuXpUuX8uSTT/Kb3/yGW2+9dbzSA4Zmgv7Xf/0XP/zhD5k5cyZ//OMfuf/++xNiqquree2119i5cyeLFy9m2bJlPPfccxgMQ49bfu973+Pf//3fuf/++5k2bRqXX345L774IhMmTEh7PtnZ2fzhD3/gpZdeYtasWTzxxBPcd999CTErV67kqaee4m9/+xtz587l4osvPquHUW02G6+++io9PT0sWrSIG2+8kVWrVvHzn/98jLN5/z5sfdTpdPzpT39i69atzJw5k7vuuov/7//7/+KfOxwOnn/+eWpqapg3bx7/9m//NuLE40y27bmgq6trzO5Xp8rxY9+Jr+P/mo7FYuGNN97g05/+NN/61reoqqri8ssvR6/Xs2HDhvgzcgCXXXYZjz32GJdeeinTpk3j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",
      "text/plain": [
       "<Figure size 480x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "'it s very cold here .'"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "translator = Translator(model.cpu(), src_tokenizer, trg_tokenizer)\n",
    "translator(u'hace mucho frio aqui .')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-05-06T03:11:41.298875400Z",
     "start_time": "2024-05-06T03:11:40.874116700Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-23T10:22:11.031391Z",
     "iopub.status.busy": "2025-01-23T10:22:11.031046Z",
     "iopub.status.idle": "2025-01-23T10:22:11.163399Z",
     "shell.execute_reply": "2025-01-23T10:22:11.162819Z",
     "shell.execute_reply.started": "2025-01-23T10:22:11.031366Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfcAAAHFCAYAAADvx7CBAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAdXBJREFUeJzt3Xd4HOW58P/vbJe0K616r7ZkW7ZxxQ1cAAOBQEJCSTgJAZKQk5w0CMmbQxqkQQ5vcoBfTho5b0ilJAFCCC3YsQEbjHHvlmSrWb33rfP8/pC98lorV2lXHt+f69oL7+w9s8+t2Z17nmeeWTSllEIIIYQQhmGKdQOEEEIIMb6kuAshhBAGI8VdCCGEMBgp7kIIIYTBSHEXQgghDEaKuxBCCGEwUtyFEEIIg5HiLoQQQhiMFHchhBDCYKS4n8SqVavQNA1N09ixY0dM2lBTUxNqw9y5c2PSBiHOxfr169E0je7u7lg35Zw98MADp/we3nHHHdxwww1RaY84M5PhmB6tNkhxP4W77rqLpqYmZs2aFVZoNU3DZrMxdepUfvCDH3Dir/ju3buXW265hfT0dOx2O2VlZXznO99hcHAwLG7nzp184AMfICMjA4fDQVFRER/5yEdobW0FID8/n6amJu69996o5Tzejv3dYvVlErG1bNkympqaSEpKinVTztlXv/pV1q5dG+tmiHNwsmP68Y9NmzaF1hkaGuL++++nrKwMu91OWloaN998M3v37g3b9uDgIPfddx9TpkzB4XCQnp7OypUreeGFF0Ixzz33HJs3b57wPC0T/g7nufj4eLKyssKWrVmzhpkzZ+L1etmwYQOf/vSnyc7O5lOf+hQAmzZtYvXq1axevZqXXnqJzMxMNm/ezL333svatWtZt24dNpuNtrY2rrjiCq677jpee+013G43NTU1/P3vf2dgYAAAs9lMVlYWTqcz6rkLMR5sNtuo79D5yul0ynfxPHeyY/rxUlNTAfB6vaxevZq6ujp+8pOfsHjxYlpaWnjooYdYvHgxa9asYcmSJQB89rOf5d133+WnP/0p5eXldHR08Pbbb9PR0RHabkpKCr29vROcJaDEmFauXKm+/OUvh55XV1crQG3fvj0s7oorrlD/8R//oZRSStd1VV5erhYuXKiCwWBY3I4dO5SmaepHP/qRUkqp559/XlksFuX3+0/Zlvvvv1/NmTPnnPI5V8FgUD344IOqqKhIORwOddFFF6m//OUvSimlOjs71b/927+ptLQ05XA41NSpU9VvfvMbpZRSQNhj5cqVSimlNm/erFavXq1SU1NVYmKiWrFihdq6dWus0jsrZ/s3OZ+tXLlSfeELX1Bf/vKXldvtVhkZGerxxx9X/f396o477lBOp1NNmTJFvfzyy0oppdatW6cA1dXVFduGn4Zf/epXKjs7e9R39wMf+IC68847R30PA4GAuueee1RSUpJKSUlRX/va19QnPvEJ9cEPfjAU88orr6hLLrkkFPP+979fVVVVRSkjcbzTPaYf70c/+pHSNE3t2LEjbHkwGFQLFy5U5eXlStd1pZRSSUlJ6re//e0p23E673uuZFj+HG3ZsoWtW7eyePFiAHbs2MG+ffv4yle+gskU/uedM2cOq1ev5qmnngIgKyuLQCDA888/P2pYfzJ66KGH+P3vf88vf/lL9u7dyz333MPHP/5x3njjDb797W+zb98+XnnlFfbv388vfvEL0tLSAEJDUGvWrKGpqYnnnnsOgL6+Pm6//XY2bNjApk2bKC0t5dprr6Wvry9mOZ6ps/2bnO9+97vfkZaWxubNm/niF7/I5z73OW6++WaWLVvGtm3buOqqq7jttttGXYaa7G6++WY6OjpYt25daFlnZyevvvoqH/vYx0bF/+QnP+G3v/0tv/nNb9iwYQOdnZ08//zzYTEDAwN85StfYcuWLaxduxaTycSHPvQhdF2f8HzEuXvyySe58sormTNnTthyk8nEPffcw759+9i5cycwfEx/+eWXJ8cxbMJOGwxgrLO8uLg4lZCQoKxWqwLUZz7zmVDM008/fdIzsi996UsqLi4u9Pwb3/iGslgsKiUlRb3vfe9TDz/8sGpubh61Xqx77h6PR8XHx6u33347bPmnPvUpdeutt6rrr79e3XnnnRHXPd2z1GAwqFwul3rxxRfHq9kT6lz+JuezlStXqksvvTT0PBAIqISEBHXbbbeFljU1NSlAvfPOO+dVz10ppT74wQ+qT37yk6Hnv/rVr1ROTo4KBoOjvofZ2dnq4YcfDj33+/0qLy8vrOd+ora2NgWo3bt3T0TzxUmc6ph+/OMYh8MRts7xtm3bpgD1zDPPKKWUeuONN1ReXp6yWq1q4cKF6u6771YbNmwYtZ703CepZ555hh07drBz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",
      "text/plain": [
       "<Figure size 560x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "'this is my life .'"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "translator(u'esta es mi vida.')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:29:53.684237Z",
     "iopub.status.busy": "2025-01-25T07:29:53.683880Z",
     "iopub.status.idle": "2025-01-25T07:29:53.687422Z",
     "shell.execute_reply": "2025-01-25T07:29:53.686923Z",
     "shell.execute_reply.started": "2025-01-25T07:29:53.684214Z"
    },
    "scrolled": true,
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('hola .', 'hi .')\n"
     ]
    }
   ],
   "source": [
    "for i in test_ds:\n",
    "    print(i)\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T07:50:02.528986Z",
     "iopub.status.busy": "2025-01-25T07:50:02.528628Z",
     "iopub.status.idle": "2025-01-25T07:50:02.790461Z",
     "shell.execute_reply": "2025-01-25T07:50:02.789958Z",
     "shell.execute_reply.started": "2025-01-25T07:50:02.528953Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_300/2788061694.py:2: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
      "  model.load_state_dict(torch.load(f\"./checkpoints/translate-seq2seq/best.ckpt\", map_location=device))\n"
     ]
    }
   ],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-25T09:46:46.763875Z",
     "iopub.status.busy": "2025-01-25T09:46:46.763509Z",
     "iopub.status.idle": "2025-01-25T09:51:39.338738Z",
     "shell.execute_reply": "2025-01-25T09:51:39.338223Z",
     "shell.execute_reply.started": "2025-01-25T09:46:46.763851Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_300/472989537.py:2: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
      "  model.load_state_dict(torch.load(f\"./checkpoints/translate-seq2seq/best.ckpt\", map_location=\"cpu\"))\n",
      "/usr/local/lib/python3.10/site-packages/nltk/translate/bleu_score.py:577: UserWarning: \n",
      "The hypothesis contains 0 counts of 2-gram overlaps.\n",
      "Therefore the BLEU score evaluates to 0, independently of\n",
      "how many N-gram overlaps of lower order it contains.\n",
      "Consider using lower n-gram order or use SmoothingFunction()\n",
      "  warnings.warn(_msg)\n",
      "/usr/local/lib/python3.10/site-packages/nltk/translate/bleu_score.py:577: UserWarning: \n",
      "The hypothesis contains 0 counts of 3-gram overlaps.\n",
      "Therefore the BLEU score evaluates to 0, independently of\n",
      "how many N-gram overlaps of lower order it contains.\n",
      "Consider using lower n-gram order or use SmoothingFunction()\n",
      "  warnings.warn(_msg)\n",
      "/usr/local/lib/python3.10/site-packages/nltk/translate/bleu_score.py:577: UserWarning: \n",
      "The hypothesis contains 0 counts of 4-gram overlaps.\n",
      "Therefore the BLEU score evaluates to 0, independently of\n",
      "how many N-gram overlaps of lower order it contains.\n",
      "Consider using lower n-gram order or use SmoothingFunction()\n",
      "  warnings.warn(_msg)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "0.7082455254952078"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model = Sequence2Sequence(len(src_word2idx), len(trg_word2idx))\n",
    "model.load_state_dict(torch.load(f\"./checkpoints/translate-seq2seq/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "class Translator:\n",
    "    def __init__(self, model, src_tokenizer, trg_tokenizer):\n",
    "        self.model = model\n",
    "        self.model.eval() # 切换到验证模式\n",
    "        self.src_tokenizer = src_tokenizer\n",
    "        self.trg_tokenizer = trg_tokenizer\n",
    "\n",
    "    def __call__(self, sentence):\n",
    "        sentence = preprocess_sentence(sentence) # 预处理句子，标点符号处理等\n",
    "        encoder_input, attn_mask = self.src_tokenizer.encode(\n",
    "            [sentence.split()],\n",
    "            padding_first=True,\n",
    "            add_bos=True,\n",
    "            add_eos=True,\n",
    "            return_mask=True,\n",
    "            ) # 对输入进行编码，并返回encode_piadding_mask\n",
    "        encoder_input = torch.Tensor(encoder_input).to(dtype=torch.int64) # 转换成tensor\n",
    "\n",
    "        preds, scores = model.infer(encoder_input=encoder_input, attn_mask=attn_mask) #预测\n",
    "\n",
    "        trg_sentence = self.trg_tokenizer.decode([preds], split=True, remove_eos=False)[0] #通过tokenizer转换成文字\n",
    "\n",
    "        return \" \".join(trg_sentence[:-1])\n",
    "\n",
    "from nltk.translate.bleu_score import sentence_bleu\n",
    "\n",
    "def evaluate_bleu_on_test_set(test_data, translator):\n",
    "    \"\"\"\n",
    "    在测试集上计算平均 BLEU 分数。\n",
    "    :param test_data: 测试集数据，格式为 [(src_sentence, [ref_translation1, ref_translation2, ...]), ...]\n",
    "    :param translator: 翻译器对象（Translator 类的实例）\n",
    "    :return: 平均 BLEU 分数\n",
    "    \"\"\"\n",
    "    total_bleu = 0.0\n",
    "    num_samples = len(test_data)\n",
    "    i=0\n",
    "    for src_sentence, ref_translations in test_data:\n",
    "        # 使用翻译器生成翻译结果\n",
    "        candidate_translation = translator(src_sentence)\n",
    "\n",
    "        # 计算 BLEU 分数\n",
    "        bleu_score = sentence_bleu([ref_translations.split()], candidate_translation.split(),weights=(1, 0, 0, 0))\n",
    "        total_bleu += bleu_score\n",
    "\n",
    "        # 打印当前句子的 BLEU 分数（可选）\n",
    "        # print(f\"Source: {src_sentence}\")\n",
    "        # print(f\"Reference: {ref_translations}\")\n",
    "        # print(f\"Candidate: {candidate_translation}\")\n",
    "        # print(f\"BLEU: {bleu_score:.4f}\")\n",
    "        # print(\"-\" * 50)\n",
    "        # i+=1\n",
    "        # if i>10:\n",
    "        #     break\n",
    "    # 计算平均 BLEU 分数\n",
    "    avg_bleu = total_bleu / num_samples\n",
    "    return avg_bleu\n",
    "translator = Translator(model.cpu(), src_tokenizer, trg_tokenizer)\n",
    "evaluate_bleu_on_test_set(test_ds, translator)"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "gpuType": "V100",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "267a9f8d838c4649b208914938b1bcff": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "HBoxModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HBoxModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HBoxView",
      "box_style": "",
      "children": [
       "IPY_MODEL_a9552c62dcb4441fbca47f89e939759d",
       "IPY_MODEL_7dc472c2f73f4443a0e8437074e67476",
       "IPY_MODEL_c46cefacefb54be7ad60ca93855de3ca"
      ],
      "layout": "IPY_MODEL_9ebbd2d91c9849baaa85cff5b2b048e1"
     }
    },
    "296304e90e43440094ba467cafb20896": {
     "model_module": "@jupyter-widgets/base",
     "model_module_version": "1.2.0",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "63c6ee4650cb4a938e9f325113e259d1": {
     "model_module": "@jupyter-widgets/base",
     "model_module_version": "1.2.0",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "7d21bf5788794ed2b0b4dc4a98a5d2e9": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "ProgressStyleModel",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "ProgressStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "bar_color": null,
      "description_width": ""
     }
    },
    "7dc472c2f73f4443a0e8437074e67476": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "FloatProgressModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "FloatProgressModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "ProgressView",
      "bar_style": "danger",
      "description": "",
      "description_tooltip": null,
      "layout": "IPY_MODEL_863e7ad581894502979884fe0e9e412e",
      "max": 33420,
      "min": 0,
      "orientation": "horizontal",
      "style": "IPY_MODEL_7d21bf5788794ed2b0b4dc4a98a5d2e9",
      "value": 7599
     }
    },
    "863e7ad581894502979884fe0e9e412e": {
     "model_module": "@jupyter-widgets/base",
     "model_module_version": "1.2.0",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "95d9e57c587f43e6ba5f656f2b2e5c71": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "DescriptionStyleModel",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "DescriptionStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "description_width": ""
     }
    },
    "9ebbd2d91c9849baaa85cff5b2b048e1": {
     "model_module": "@jupyter-widgets/base",
     "model_module_version": "1.2.0",
     "model_name": "LayoutModel",
     "state": {
      "_model_module": "@jupyter-widgets/base",
      "_model_module_version": "1.2.0",
      "_model_name": "LayoutModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "LayoutView",
      "align_content": null,
      "align_items": null,
      "align_self": null,
      "border": null,
      "bottom": null,
      "display": null,
      "flex": null,
      "flex_flow": null,
      "grid_area": null,
      "grid_auto_columns": null,
      "grid_auto_flow": null,
      "grid_auto_rows": null,
      "grid_column": null,
      "grid_gap": null,
      "grid_row": null,
      "grid_template_areas": null,
      "grid_template_columns": null,
      "grid_template_rows": null,
      "height": null,
      "justify_content": null,
      "justify_items": null,
      "left": null,
      "margin": null,
      "max_height": null,
      "max_width": null,
      "min_height": null,
      "min_width": null,
      "object_fit": null,
      "object_position": null,
      "order": null,
      "overflow": null,
      "overflow_x": null,
      "overflow_y": null,
      "padding": null,
      "right": null,
      "top": null,
      "visibility": null,
      "width": null
     }
    },
    "a9552c62dcb4441fbca47f89e939759d": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "HTMLModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HTMLModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HTMLView",
      "description": "",
      "description_tooltip": null,
      "layout": "IPY_MODEL_63c6ee4650cb4a938e9f325113e259d1",
      "placeholder": "​",
      "style": "IPY_MODEL_95d9e57c587f43e6ba5f656f2b2e5c71",
      "value": " 23%"
     }
    },
    "c3d8fddae7354c278c8e88291dbcee8d": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "DescriptionStyleModel",
     "state": {
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "DescriptionStyleModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/base",
      "_view_module_version": "1.2.0",
      "_view_name": "StyleView",
      "description_width": ""
     }
    },
    "c46cefacefb54be7ad60ca93855de3ca": {
     "model_module": "@jupyter-widgets/controls",
     "model_module_version": "1.5.0",
     "model_name": "HTMLModel",
     "state": {
      "_dom_classes": [],
      "_model_module": "@jupyter-widgets/controls",
      "_model_module_version": "1.5.0",
      "_model_name": "HTMLModel",
      "_view_count": null,
      "_view_module": "@jupyter-widgets/controls",
      "_view_module_version": "1.5.0",
      "_view_name": "HTMLView",
      "description": "",
      "description_tooltip": null,
      "layout": "IPY_MODEL_296304e90e43440094ba467cafb20896",
      "placeholder": "​",
      "style": "IPY_MODEL_c3d8fddae7354c278c8e88291dbcee8d",
      "value": " 7599/33420 [09:31&lt;23:51, 18.03it/s, epoch=3, loss=1.2, val_loss=1.26]"
     }
    }
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
